package no.hib.mod250.chat.client.controller;

import java.util.ArrayList;
import java.util.List;

import no.hib.mod250.chat.client.model.Model;
import no.hib.mod250.chat.client.service.ChatService;
import no.hib.mod250.chat.client.service.ChatServiceAsync;
import no.hib.mod250.chat.client.service.ChatService.Result;
import no.hib.mod250.chat.client.util.ObjectWithEvent;
import no.hib.mod250.chat.client.view.ChatView;
import no.hib.mod250.chat.client.view.View;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Panel;
import com.tractionsoftware.gwt.user.client.Viewport;

public class ChatController extends Controller {

	// CLASSES

	// ... RPC

	/**
	 * Task that returns ChatService.Result
	 */
	public abstract class ResultTask extends Controller.Task implements
			AsyncCallback<ChatService.Result> {

		@Override
		public void onFailure(Throwable caught) {
			taskQueue.taskFailed(this, false);
		}

		@Override
		public void onSuccess(ChatService.Result result) {
			taskQueue.taskSucceeded(this);

			// Notify data result
			doNotifyDataResult(result);
		}

	}

	public class DoInitializeTask extends ResultTask {

		@Override
		public void execute() {
			service.doInitialize(this);
		}

	}

	public class DoMessageNewTask extends ResultTask {

		private final String message;

		public DoMessageNewTask(String message) {
			this.message = message;
		}

		@Override
		public void execute() {
			// Disable Chat while sending message
			getIsChatEnabled().setObject(false);

			// Send message
			service.doMessageNew(lastUpdate, message, this);
		}

		@Override
		public void onFailure(Throwable caught) {
			if (caught instanceof ChatService.UnauthorizedException) {
				Window.alert("Unauthorized Exception");
			} else {
				super.onFailure(caught);
			}
		}

		@Override
		public void onSuccess(ChatService.Result result) {
			super.onSuccess(result);

			// Reset update timer
			doTimerUpdateReset();
		}

	}

	public class DoMessageDeleteTask extends ResultTask {

		private final long id;

		public DoMessageDeleteTask(long id) {
			this.id = id;
		}

		@Override
		public void execute() {
			service.doMessageDelete(lastUpdate, id, this);
		}

		@Override
		public void onFailure(Throwable caught) {
			if (caught instanceof ChatService.UnauthorizedException) {
				Window.alert("Unauthorized Exception");
			} else if (caught instanceof ChatService.IllegalMessageException) {
				Window.alert("Message does not exist");
			} else {
				super.onFailure(caught);
			}
		}

		@Override
		public void onSuccess(ChatService.Result result) {
			super.onSuccess(result);

			// Reset update timer
			doTimerUpdateReset();
		}

	}

	public class DoMessagesDeleteTask extends ResultTask {

		@Override
		public void execute() {
			service.doMessagesDelete(lastUpdate, this);
		}

		@Override
		public void onFailure(Throwable caught) {
			if (caught instanceof ChatService.UnauthorizedException) {
				Window.alert("Unauthorized Exception");
			} else {
				super.onFailure(caught);
			}
		}

		@Override
		public void onSuccess(Result result) {
			super.onSuccess(result);

			// Reset update timer
			doTimerUpdateReset();
		}

	}

	public class DoUpdateTask extends ResultTask {

		@Override
		public void execute() {
			service.doUpdate(lastUpdate, this);
		}

	}

	public class DoUserBusyTask extends Controller.Task implements
			AsyncCallback<Void> {

		@Override
		public void execute() {
			service.doUserBusy(this);
		}

		@Override
		public void onFailure(Throwable caught) {
			taskQueue.taskFailed(this, false);
		}

		@Override
		public void onSuccess(Void result) {
			taskQueue.taskSucceeded(this);
		}

	}

	public class DoUserAvailableTask extends ResultTask {

		@Override
		public void execute() {
			service.doUserAvailable(lastUpdate, this);
		}

	}

	public class DoUserOfflineTask extends Controller.Task implements
			AsyncCallback<Void> {

		@Override
		public void execute() {
			service.doUserOffline(this);
		}

		@Override
		public void onFailure(Throwable caught) {
			taskQueue.taskFailed(this, false);
		}

		@Override
		public void onSuccess(Void result) {
			taskQueue.taskSucceeded(this);
		}

	}

	// ... /RPC

	// /CLASSES

	// VARIABLES

	public static final int TIMER_UPDATE = 5000; // ms

	// ... OBSERVERS

	protected List<ChatView.MessageClickObserver> messageClickObservers = new ArrayList<ChatView.MessageClickObserver>();

	// ... /OBSERVERS

	protected ChatServiceAsync service;
	protected long lastUpdate = 0L;
	protected Timer timerUpdate;
	protected Model.UserAdmin user = null;
	protected ObjectWithEvent<Boolean> updateWhileBusy = new ObjectWithEvent<Boolean>(
			true);
	protected ObjectWithEvent<Boolean> timerUpdateEnabled = new ObjectWithEvent<Boolean>(
			true);
	protected ObjectWithEvent<Integer> unreadCounter = new ObjectWithEvent<Integer>(
			0);
	protected ObjectWithEvent<Boolean> isAvailable = new ObjectWithEvent<Boolean>(
			true);
	protected ObjectWithEvent<Boolean> isChatEnabled = new ObjectWithEvent<Boolean>(
			true);

	// /VARIABLES

	// CONSTRUCTOR

	public ChatController(EntryPoint entryPoint, Panel root,
			ChatServiceAsync service) {
		super(entryPoint, root);
		this.service = service;
		this.view = new ChatView(this);
	}

	// /CONSTRUCTOR

	// FUNCTIONS

	// ... GET/SET

	public long getLastUpdate() {
		return this.lastUpdate;
	}

	public Model.UserAdmin getChatUser() {
		return this.user;
	}

	public ObjectWithEvent<Boolean> getUpdateWhileBusy() {
		return updateWhileBusy;
	}

	public ObjectWithEvent<Boolean> getTimerUpdateEnabled() {
		return timerUpdateEnabled;
	}

	public ObjectWithEvent<Integer> getUnreadCounter() {
		return unreadCounter;
	}

	public ObjectWithEvent<Boolean> getIsAvailable() {
		return isAvailable;
	}

	public ObjectWithEvent<Boolean> getIsChatEnabled() {
		return isChatEnabled;
	}

	// ... /GET/SET

	// ... ADD

	public void addMessageClickObserver(ChatView.MessageClickObserver observer) {
		messageClickObservers.add(observer);
	}

	// ... /ADD

	// ... DO

	@Override
	public void doLoad() {
		// Load events
		doEvents();

		// Initialize
		taskQueue.post(new DoInitializeTask());

		// Start update timer
		doTimerUpdateStart();

		// Add view to root
		this.root.add(this.view);
	}

	private void doEvents() {
		Viewport.get().addFocusHandler(new FocusHandler() {
			@Override
			public void onFocus(FocusEvent event) {
				getIsAvailable().setObject(true);
			}
		});

		Viewport.get().addBlurHandler(new BlurHandler() {
			@Override
			public void onBlur(BlurEvent event) {
				getIsAvailable().setObject(false);
			}
		});

		Window.addCloseHandler(new CloseHandler<Window>() {
			@Override
			public void onClose(CloseEvent<Window> event) {
				doUserOffline();
			}
		});

		getIsAvailable().addListener(new ObjectWithEvent.Listener<Boolean>() {

			@Override
			public void onChanged(Boolean value) {
				if (!value) {
					doUserBusy();
					if (!getUpdateWhileBusy().getObject()) {
						doTimerUpdatePause();
					}
					// Reset unread counter
					getUnreadCounter().setObject(0);
				} else {
					doUserAvailable();
					doTimerUpdateStart();
				}
			}
		});

		getTimerUpdateEnabled().addListener(
				new ObjectWithEvent.Listener<Boolean>() {

					@Override
					public void onChanged(Boolean value) {
						if (value) {
							doTimerUpdateStart();
						} else {
							doTimerUpdatePause();
						}
					}
				});
	}

	public void doMessageNew(String message) {
		if (user != null && user.isLoggedIn()) {
			taskQueue.post(new DoMessageNewTask(message));
		}
	}

	public void doMessageDelete(long id) {
		if (user != null && user.isLoggedIn()) {
			taskQueue.post(new DoMessageDeleteTask(id));
		}
	}

	public void doMessagesDelete() {
		if (user != null && user.isLoggedIn() && user.isAdmin()) {
			taskQueue.post(new DoMessagesDeleteTask());
		}
	}

	public void doUserBusy() {
		if (user != null && user.isLoggedIn()) {
			taskQueue.post(new DoUserBusyTask());
		}
	}

	public void doUserAvailable() {
		if (user != null && user.isLoggedIn()) {
			taskQueue.post(new DoUserAvailableTask());
		}
	}

	public void doUserOffline() {
		if (user != null && user.isLoggedIn()) {
			taskQueue.post(new DoUserOfflineTask());
		}
	}

	// ... ... TIMER

	public void doTimerUpdateStart() {
		if (getTimerUpdateEnabled().getObject()) {
			if (timerUpdate == null) {
				timerUpdate = new Timer() {
					@Override
					public void run() {
						taskQueue.post(new DoUpdateTask());
					}
				};
			}

			timerUpdate.scheduleRepeating(TIMER_UPDATE);
		}
	}

	public void doTimerUpdatePause() {
		if (timerUpdate != null) {
			timerUpdate.cancel();
		}
	}

	public void doTimerUpdateReset() {
		doTimerUpdatePause();
		doTimerUpdateStart();
	}

	// ... ... /TIMER

	// ... ... NOTIFY

	public void doNotifyDataResult(ChatService.Result result) {
		// Set User
		user = result.getUser();

		// Set new update date
		lastUpdate = result.getLastUpdated();

		// Increase unread counter if busy
		if (!getIsAvailable().getObject()) {
			int messageCounter = result.getMessages().size();
			int counter = getUnreadCounter().getObject();
			getUnreadCounter().setObject(counter + messageCounter);
		}

		// Enable chat if logged in
		if (user != null && user.isLoggedIn()) {
			getIsChatEnabled().setObject(true);
		}

		for (View.DataObserver dataObserver : dataObservers) {
			dataObserver.onDataResult(result);
		}
	}

	public void doNotifyMessageClick(Model.Message message) {
		for (ChatView.MessageClickObserver messageClickObserver : messageClickObservers) {
			messageClickObserver.onMessageClick(message);
		}
	}

	// ... ... /NOTIFY

	// ... /DO

	// ... ON

	@Override
	public void onControllerLoadFailed() {

	}

	@Override
	public void onControllerLoaded() {
	}

	// ... /ON

	// /FUNCTIONS

}
